﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.IdentityModel.SecurityTokenService;
using Microsoft.IdentityModel.Claims;
using Microsoft.IdentityModel.Configuration;
using Microsoft.IdentityModel.Tokens;
using System.ServiceModel;
using System.IdentityModel.Tokens;
using System.Security.Cryptography.X509Certificates;
using System.Web;
using Veracruz.IdentityModel;
using System.IdentityModel.Selectors;
using Microsoft.IdentityModel.Tokens.Saml11;

namespace Veracruz.Facebook.IdentityModel
{
    public class ActAsIssuerNameRegistry : IssuerNameRegistry
    {
        /// <summary>
        /// Overrides the base class. Validates the given issuer token. For a incoming SAML token
        /// the issuer token is the Certificate that signed the SAML token.
        /// </summary>
        /// <param name="securityToken">Issuer token to be validated.</param>
        /// <returns>Friendly name representing the Issuer.</returns>
        public override string GetIssuerName(SecurityToken securityToken)
        {
            X509SecurityToken x509Token = securityToken as X509SecurityToken;
            if (x509Token != null)
            {
                // Warning: This sample does a simple compare of the Issuer Certificate
                // to a subject name. This is not appropriate for production use. 
                // Check your validation policy and authenticate issuers based off the policy.
                if (String.Equals(x509Token.Certificate.SubjectName.Name, "CN=STS"))
                {
                    return x509Token.Certificate.SubjectName.Name;
                }
            }

            throw new SecurityTokenException("Untrusted issuer.");
        }
    }

    public sealed class FacebookSecurityTokenService : SecurityTokenService
    {
        #region fields

        private SigningCredentials m_signingCreds;
        private EncryptingCredentials m_encryptingCreds;

        #endregion
        #region ctors

        public FacebookSecurityTokenService(SecurityTokenServiceConfiguration configuration)
            : base(configuration)
        {
            // Setup our certificate the STS is going to use to sign the issued tokens
            m_signingCreds = new X509SigningCredentials(CertificateHelper.GetCertificate(StoreName.My, StoreLocation.LocalMachine, Constants.SigningCertificateName));

            // We only support a single RP identity represented by the _encryptingCreds
            m_encryptingCreds = new X509EncryptingCredentials(CertificateHelper.GetCertificate(StoreName.My, StoreLocation.LocalMachine, Constants.EncryptingCertificateName));
        }

        #endregion
        #region methods

        private void AddFacebookClaims(ClaimsIdentity l_claimIdentity)
        {
 
        }
        /// <summary>
        /// Do a deep-copy of IClaimsIdentity except the issuer.
        /// </summary>
        /// <param name="srcIdentity"></param>
        /// <param name="dstIdentity"></param>
        private void CopyClaims(IClaimsIdentity srcIdentity, IClaimsIdentity dstIdentity)
        {
            foreach (Claim claim in (from c in srcIdentity.Claims
                                     where c.ClaimType == ClaimTypes.PrimarySid || c.ClaimType == System.IdentityModel.Claims.ClaimTypes.Name || 
                                     c.ClaimType == System.IdentityModel.Claims.ClaimTypes.Gender || c.ClaimType == System.IdentityModel.Claims.ClaimTypes.Locality ||
                                     c.ClaimType == IdentityClaimDefinitions.AuthenticationTokenClaim || c.ClaimType == IdentityClaimDefinitions.SessionKeyClaim ||
                                     c.ClaimType == IdentityClaimDefinitions.UniqueIdentifierClaim
                                     select c))
            {
                // We don't copy the issuer because it is not needed in this case. The STS always issues claims
                // using its own identity.
                Claim newClaim = new Claim(claim.ClaimType, claim.Value, claim.ValueType);

                // copy all claim properties
                foreach (string key in claim.Properties.Keys)
                {
                    newClaim.Properties.Add(key, claim.Properties[key]);
                }

                // add claim to the destination identity
                dstIdentity.Claims.Add(newClaim);
            }

            // Recursively copy claims from the source identity delegates
            if (srcIdentity.Delegate != null)
            {
                dstIdentity.Delegate = new ClaimsIdentity();
                CopyClaims(srcIdentity.Delegate, dstIdentity.Delegate);
            }
        }
        protected override Scope GetScope(IClaimsPrincipal principal, RequestSecurityToken request)
        {
            // Create the scope using the request AppliesTo address and the STS signing certificate
            Scope scope = new Scope(request, m_signingCreds);

            // We only support a single RP identity represented by CN=localhost. Set the RP certificate for encryption
            scope.EncryptingCredentials = m_encryptingCreds;

            // Set the replyTo address. In WS-Federation passive case this value is used as the endpoint
            // where the user is redirected to.
            scope.ReplyToAddress = scope.AppliesToAddress;

            return scope;
        }

        #endregion

        //protected override IClaimsIdentity GetOutputClaimsIdentity(IClaimsPrincipal principal, RequestSecurityToken request, Scope scope)
        //{
        //    throw new NotImplementedException();
        //}

        protected override IClaimsIdentity GetOutputClaimsIdentity(IClaimsPrincipal principal, RequestSecurityToken request, Scope scope)
        {
            IClaimsIdentity l_callerIdentity = (IClaimsIdentity)principal.Identity;

            IClaimsIdentity l_outputIdentity = new ClaimsIdentity();

            // HttpContext.Current helps to determine in which federation mode we are : Passive or Active.
            if (HttpContext.Current == null && request.ActAs == null)
            {
                throw new SecurityTokenException("Whathth : A Facebook login is required before using this Facebook Security Token Service.");
            }
            if (HttpContext.Current != null && request.ActAs == null)
            {
                string l_authenticationToken = HttpContext.Current.Request[Constants.AuthentificationToken];

                Session session;
                UserInfo l_userInfo = FacebookHelper.GetUserInfo(l_authenticationToken, out session);

                l_outputIdentity.Claims.Add(new Claim(System.IdentityModel.Claims.ClaimTypes.Name, l_userInfo.Name));
                l_outputIdentity.Claims.Add(new Claim(IdentityClaimDefinitions.FirstNameClaim, l_userInfo.FirstName));
                l_outputIdentity.Claims.Add(new Claim(IdentityClaimDefinitions.LastNameClaim, l_userInfo.LastName));
                l_outputIdentity.Claims.Add(new Claim(IdentityClaimDefinitions.AuthenticationSchemeClaim, l_userInfo.AuthenticationSchemeClaim.ToString()));
                l_outputIdentity.Claims.Add(new Claim(IdentityClaimDefinitions.AuthenticationTokenClaim, l_authenticationToken));
                l_outputIdentity.Claims.Add(new Claim(IdentityClaimDefinitions.SessionKeyClaim, session.SessionKey));
                l_outputIdentity.Claims.Add(new Claim(IdentityClaimDefinitions.UniqueIdentifierClaim, session.UserId.ToString()));
                l_outputIdentity.Claims.Add(new Claim(System.IdentityModel.Claims.ClaimTypes.Gender, l_userInfo.Sex));
                l_outputIdentity.Claims.Add(new Claim(System.IdentityModel.Claims.ClaimTypes.Locality, l_userInfo.HomeTown));
                l_outputIdentity.Claims.Add(new Claim(System.IdentityModel.Claims.ClaimTypes.Email, l_userInfo.Email));

                //HACK: look it up from facebook data store
                l_outputIdentity.Claims.Add(new Claim(RoleClaimDefinitions.ClaimType, RoleClaimDefinitions.MobsterRoleClaim));
                l_outputIdentity.Claims.Add(new Claim(RoleClaimDefinitions.ClaimType, RoleClaimDefinitions.CitizenRoleClaim));

                l_outputIdentity.RoleClaimTypes.Add(RoleClaimDefinitions.ClaimType);
            }
            else if (request.ActAs != null)
            {
                // setup the token handlers to process the ActAs token
                request.ActAs.SecurityTokenHandlers = new SecurityTokenHandlerCollection();
                SamlSecurityTokenRequirement actAsTokenRequirement = new SamlSecurityTokenRequirement();
                // typically, audience checks on ActAs tokens will fail because this STS is not the original intended recipient
                actAsTokenRequirement.AudienceUriMode = AudienceUriMode.Never;
                // add the ActAs issuer name registry to validate the issuer of the ActAs token
                actAsTokenRequirement.IssuerNameRegistry = new ActAsIssuerNameRegistry();

                request.ActAs.SecurityTokenHandlers.Add(new Saml11SecurityTokenHandler(actAsTokenRequirement));


                // If there is an ActAs token in the RST, add and return the claims from it as the top-most identity
                // and put the caller's identity into the Delegate property of this identity.

                CopyClaims(l_callerIdentity, l_outputIdentity);

                IClaimsIdentity actAsIdentity = new ClaimsIdentity();
                CopyClaims(request.ActAs.Subject, actAsIdentity);

                // Find the last delegate in the actAs identity
                IClaimsIdentity lastActingVia = actAsIdentity;
                while (lastActingVia.Delegate != null)
                {
                    lastActingVia = lastActingVia.Delegate;
                }
                // Put the caller's identity as the last delegate to the ActAs identity
                lastActingVia.Delegate = l_outputIdentity;

                l_outputIdentity = actAsIdentity;
            }

            return l_outputIdentity;
        }
    }
}
